import os
import re
import json

class Utils():
    @staticmethod
    def get_file_line_count(file_path):
        with open(file_path, encoding="utf-8") as f:
            return sum(1 for _ in f)
        
    @staticmethod
    def create_dir(dir_path=None, file_path=None):
        dir_path = dir_path if dir_path else os.path.dirname(file_path)
        if dir_path and not os.path.exists(dir_path):
            os.makedirs(dir_path)

    @staticmethod
    def read_json_file(file_path, default_value=None):
        if not os.path.exists(file_path):
            return default_value
        try:
            with open(file_path, encoding="utf-8") as f:
                return json.load(f)
        except Exception as e:
            return default_value
        
    @staticmethod
    def write_json_file(file_path, data):
        Utils.create_dir(file_path=file_path)
        with open(file_path, 'w', encoding="utf-8") as f:
            json.dump(data, f, indent=2, ensure_ascii=False)
    

class FileHelper(Utils):
    def __init__(self) -> None:
        self.script_dir = os.path.dirname(__file__)
        self.work_dir = os.path.abspath(os.path.join(self.script_dir, ".."))
        self.json_files_data_filename = os.path.join(self.script_dir, 'data/files_data.json')
        self.function_list_filename = os.path.join(self.script_dir, 'data/function_list.json')
        self.function_calls_filename = os.path.join(self.script_dir, 'data/function_calls.json')

    def get_all_files_recursively(self, dir_path):
        files = []
        for file in os.listdir(dir_path):
            full_path = os.path.join(dir_path, file)
            if os.path.isdir(full_path):
                # if file in ["git-gui", "gitweb", "gitk-git"]:
                #     continue
                # if file not in ["builtin"]:
                #     continue
                files.extend(self.get_all_files_recursively(full_path))
            else:
                if not file.endswith(".c"):
                    continue
                files.append(full_path)
        return files

    def get_files_data(self):
        files = []
        all_files = self.get_all_files_recursively(self.work_dir)
        for file in all_files:
            print(f"======================> {file}")
            files.append({
                "filename": file.replace(self.work_dir + os.sep, ""),
                "line_count": self.get_file_line_count(file),
                "function_list": self.get_function_list_from_c_file(file),
                "functions_calls_list": self.get_all_function_calls_from_c_file(file),
            })

        self.write_json_file(self.json_files_data_filename, {
            "files": files,
            "files_count": len(files),
            "total_lines": self.get_total_line_count(files),
            "functions_count": self.get_total_function_count(files),
        })
        
        self.update_function_call_count()

    def get_total_line_count(self, files):
        total_line_count = 0
        for file in files:
            total_line_count += file["line_count"]
        return total_line_count
    
    def get_total_function_count(self, files):
        total_function_count = 0
        for file in files:
            total_function_count += len(file["function_list"])
        return total_function_count
    
    def update_function_call_count(self):
        files_data = self.read_json_file(self.json_files_data_filename)
        if not files_data:
            return
        
        call_funcs = {}
        for file in files_data["files"]:            
            for func_name, count in file["functions_calls_list"].items():
                if func_name not in call_funcs:
                    call_funcs[func_name] = 0
                call_funcs[func_name] += count

        for file in files_data["files"]:
            for function in file["function_list"]:
                if function["name"] in call_funcs:
                    function["called_count"] = call_funcs[function["name"]]
        
        self.write_json_file(self.json_files_data_filename, files_data)
    
    def get_function_list_from_c_file(self, file_path):
        function_list = []
        func_info = {}
        with open(file_path, encoding="utf-8") as f:
            lines = f.readlines()
            for index, line in enumerate(lines):
                if func_info and line.startswith("}"):
                    func_info["end_line"] = index
                    func_info["line_count"] = func_info["end_line"] - func_info["star_line"] + 1
                    function_list.append(func_info)
                    func_info = {}

                if not line or line[0] in ["#", "/", " ", "\t", "{", "}", "\n", '"', "'", ";"]:
                    continue
                matched = re.match(r"^([^\s#/=]+)\s+(([^=]+)\s+)*([^=]+)\(", line)
                if matched and matched.group(1) not in ["typedef"]:
                    func_info = {
                        "name": matched.groups()[-1],
                        "line": line,
                        "star_line": index,
                    }
                    print(line)
        return function_list
    
    def save_all_functions_as_json(self):
        files = self.read_json_file(self.json_files_data_filename, {})
        lines = []
        for file in files["files"]:
            lines.extend(file["function_list"])
        self.write_json_file(self.function_list_filename, lines)

    def get_all_function_calls_from_c_file(self, file_path):
        calls_list = {}
        with open(file_path, encoding="utf-8") as f:
            lines = f.readlines()
            for index, line in enumerate(lines):
                if not line or line[0] in ["#", "/", "{", "}", "\n", '"', "'", ";"]:
                    continue
                if line[0] not in [" ", "\t", "("]:
                    continue
                matched = re.match(r"\s+([a-zA-Z0-9-_*&]+)\(", line)
                if matched:
                    func_name = matched.groups()[0]
                    if func_name not in calls_list:
                        calls_list[func_name] = 0
                    calls_list[func_name] += 1
                    print(f"line_num:{index}, func_name: {func_name}, count: {calls_list[func_name]}")
        return calls_list

    def get_top_100_most_called_functions(self):
        files_data = self.read_json_file(self.json_files_data_filename)
        if not files_data:
            return
        functions = []
        for file in files_data["files"]:
            for function in file["function_list"]:
                functions.append({
                    "name": function["name"],
                    "called_count": function.get("called_count", 0),
                })
        functions.sort(key=lambda x: x["called_count"], reverse=True)
        print(functions[:100])

    def get_top_100_most_line_count_functions(self):
        files_data = self.read_json_file(self.json_files_data_filename)
        if not files_data:
            return
        functions = []
        for file in files_data["files"]:
            for function in file["function_list"]:
                functions.append({
                    "name": function["name"],
                    "line_count": function.get("line_count", 0),
                })
        functions.sort(key=lambda x: x["line_count"], reverse=True)
        print(functions[:100])

if __name__ == "__main__":
    fh = FileHelper()
    fh.get_files_data()
    # fh.get_top_100_most_called_functions()
    # fh.get_top_100_most_line_count_functions()

